package com.whz.gateway.configure.access.rules;

import com.whz.gateway.common.constants.FilterOrder;
import com.whz.gateway.common.constants.GatewayConstant;
import com.whz.gateway.common.web.GatewayResponseBody;
import com.whz.gateway.filter.AbstractGlobalFilter;
import com.whz.gateway.limit.IBlackRule;
import com.whz.gateway.limit.IWhiteRule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 黑白名单过滤器
 *
 * @author whz
 */
@Slf4j
@Configuration
@ConditionalOnProperty(name = "gateway.black.list.enable", havingValue = "true", matchIfMissing = true)
public class BlackWhiteRuleConfiguration {

    @Bean
    public GlobalFilter createTongyuRulesFilter(Optional<List<IWhiteRule>> iWhiteRule,
                                                Optional<List<IBlackRule>> iBlackRule) {
        return new TongyuRulesFilter(iWhiteRule, iBlackRule);
    }

    @Slf4j
    private static class TongyuRulesFilter extends AbstractGlobalFilter implements Ordered {

        private final List<IWhiteRule> iWhiteRule;

        private final List<IBlackRule> iBlackRule;

        public TongyuRulesFilter(Optional<List<IWhiteRule>> iWhiteRule, Optional<List<IBlackRule>> iBlackRule) {
            this.iWhiteRule = iWhiteRule.map(Collections::unmodifiableList).orElse(Collections.emptyList());
            this.iBlackRule = iBlackRule.map(Collections::unmodifiableList).orElse(Collections.emptyList());

            List<String> wNames = this.iWhiteRule.stream().map(IWhiteRule::getName).collect(Collectors.toList());
            log.info("Global Config ---> loaded white rules:{}", wNames);

            List<String> bNames = this.iBlackRule.stream().map(IBlackRule::getName).collect(Collectors.toList());
            log.info("Global Config ---> loaded black rules:{}", bNames);
        }

        @Override
        protected boolean shouldFilter(ServerWebExchange exchange) {
            return true;
        }

        @Override
        protected Mono<Void> apply(ServerWebExchange exchange, GatewayFilterChain chain) {
            IBlackRule iBlackRule = matchBlackList(exchange);
            if (iBlackRule != null) {
                /*
                 * 黑名单优先,这样能最大保障安全
                 */
                log.warn("[black list match] -> current path :{} ", exchange.getRequest().getPath());
                return GatewayResponseBody.builder()
                                          .code(HttpStatus.BAD_REQUEST.value())
                                          .message(HttpStatus.BAD_REQUEST.name())
                                          .error(iBlackRule.getMessage())
                                          .build()
                                          .sendToResponse(exchange.getResponse());
            }
            /*
             * 白名单无需认证和权限控制,一般为静态资源
             */
            if (matchWhiteList(exchange)) {
                exchange.getAttributes().put(GatewayConstant.USER_WHITE_LIST_ATTRIBUTE, Boolean.TRUE);
            }
            return chain.filter(exchange);
        }

        @Override
        public int getOrder() {
            return FilterOrder.BLACK_LIST_PRECEDENCE;
        }


        private boolean matchWhiteList(ServerWebExchange exchange) {
            if (iWhiteRule == null) {
                return false;
            }
            return iWhiteRule.stream().anyMatch(rule -> rule.test(exchange));
        }

        private IBlackRule matchBlackList(ServerWebExchange exchange) {
            if (iBlackRule == null) {
                return null;
            }
            return iBlackRule.stream().filter(rule -> rule.test(exchange)).findFirst().orElse(null);
        }
    }
}